//////////////////////////////////////////////////
// glParticleEmitter.cpp
//
// Author: Tarik Belabbas et Adrien Hoffer
//////////////////////////////////////////////////

#include "glParticleEmitter.h"
#include "../glTexture2D.h"
#include "../glShaderProgram.h"

using namespace std;

//------------------------------------------------------------------------------
glParticleEmitter::glParticleEmitter()
{
	m_nbParticles		= 1;
	m_advectProgram	= new glShaderProgram();
	m_renderProgram	= new glShaderProgram();

	m_advectVS	= "data/shaders/particles_advect.vs";
	m_renderVS	= "data/shaders/particles.vs";
	m_renderFS	= "data/shaders/particles.fs";

	m_init		= false;
}

//------------------------------------------------------------------------------
glParticleEmitter::~glParticleEmitter()
{
	delete( m_renderProgram );

	for ( list< glTexture2D* >::iterator it = m_textures.begin();
			it != m_textures.end(); ++it )
	{
		delete( *it );
	}
}

//------------------------------------------------------------------------------
void glParticleEmitter::initialize()
{
	glEnable( GL_VERTEX_PROGRAM_POINT_SIZE );
	glHint( GL_POINT_SMOOTH_HINT, GL_NICEST );
	glEnable( GL_POINT_SPRITE );
	glTexEnvi( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_FALSE );
	glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_LOWER_LEFT);
	//glPointSize( 100.0f );

	glDepthMask(GL_FALSE);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

	m_positions.resize( 3 * m_nbParticles, 0.0f );
	m_velocities.resize( 3 * m_nbParticles, 0.0f );
	m_lives.resize( 1 * m_nbParticles, 0.0f );

	generateBuffers();

	setupShaders();

	for ( list< glTexture2D* >::iterator it = m_textures.begin();
				it != m_textures.end(); ++it )
	{
		(*it)->loadTexture();
	}

	m_init = true;
}

//------------------------------------------------------------------------------
void glParticleEmitter::generateBuffers()
{
	// Create VBO for input on even-numbered frames and output on odd-numbered frames
	m_positionsVBO.genBuffer();
	m_positionsVBO.storeData( GL_ARRAY_BUFFER, GL_STREAM_DRAW,
			m_positions.getRawData(), m_positions.getRawSize() );

	// Bind the VBO to get the feedback
	//glBindBufferOffsetNV( GL_TRANSFORM_FEEDBACK_BUFFER_NV, 0,
	//		m_positionsVBO.getBufferName(),0 );

	// Record position attrib (no shaders, so cannot feedback varyings)
	//glTransformFeedbackAttribsNV(1,att,GL_INTERLEAVED_ATTRIBS_NV);

	// Create VBO for output on even-numbered frames and input on odd-numbered frames:
	m_positionsVBO2.genBuffer();
	m_positionsVBO2.storeData( GL_ARRAY_BUFFER, GL_STREAM_DRAW,
			NULL, m_positions.getRawSize() );



	// Create VBO for input on even-numbered frames and output on odd-numbered frames
	m_velocitiesVBO.genBuffer();
	m_velocitiesVBO.storeData( GL_ARRAY_BUFFER, GL_STREAM_DRAW,
			m_velocities.getRawData(), m_velocities.getRawSize() );

	// Create VBO for output on even-numbered frames and input on odd-numbered frames:
	m_velocitiesVBO2.genBuffer();
	m_velocitiesVBO2.storeData( GL_ARRAY_BUFFER, GL_STREAM_DRAW,
			NULL, m_velocities.getRawSize() );



	// Create VBO for input on even-numbered frames and output on odd-numbered frames
	m_livesVBO.genBuffer();
	m_livesVBO.storeData( GL_ARRAY_BUFFER, GL_STREAM_DRAW,
			m_lives.getRawData(), m_lives.getRawSize() );

	// Create VBO for output on even-numbered frames and input on odd-numbered frames:
	m_livesVBO2.genBuffer();
	m_livesVBO2.storeData( GL_ARRAY_BUFFER, GL_STREAM_DRAW,
			NULL, m_lives.getRawSize() );
}

//------------------------------------------------------------------------------
void glParticleEmitter::setupShaders()
{
	// Initialize advect program
	if ( !m_advectProgram->initialize( m_advectVS.c_str() ) )
	{
		ST_ERROR( ( "Could not initialize particles advect shaders\n" ) );
	}

	// Bind the attribute locations
	m_advectProgram->bindAttribute( 0, "Position" );
	m_advectProgram->bindAttribute( 1, "Velocity" );
	m_advectProgram->bindAttribute( 2, "Life" );

	// This needs to take place after you compile the shaders, but before you link them:
	const char* varyings[ 3 ] = { "vPosition", "vVelocity", "vLife" };
	m_advectProgram->transformFeedbackVaryings( 3, varyings, GL_SEPARATE_ATTRIBS );

	// Re-link the program
	m_advectProgram->linkProgram();

	// Initialize render program
	if ( !m_renderProgram->initialize( m_renderVS.c_str(),
			m_renderFS.c_str() ) )
	{
		ST_ERROR( ( "Could not initialize particles render shaders\n" ) );
	}

	// Bind the attribute locations
	m_renderProgram->bindAttribute( 0, "Position" );
	m_advectProgram->bindAttribute( 1, "Velocity" );
	m_renderProgram->bindAttribute( 2, "Life" );

	// Re-link the program
	m_renderProgram->linkProgram();
}

//------------------------------------------------------------------------------
void glParticleEmitter::render( unsigned int _dtTimeMs, unsigned int _timeMs )
{
	//update();

	//--------------------------------------------------------------------------
	// Performs advection
	//--------------------------------------------------------------------------
	// Set up the advection shader:
	m_advectProgram->useProgram();

	m_advectProgram->sendUniform( "EmitterPosition", m_position );
	m_advectProgram->sendUniform( "EmitterDirection", m_direction );
	m_advectProgram->sendUniform( "Time", _timeMs * 0.0000001f );
	m_advectProgram->sendUniform( "TimeStep", _dtTimeMs * 0.0000001f );

	// Specify the source buffer:
	glEnable( GL_RASTERIZER_DISCARD );
		m_positionsVBO.bindBuffer( GL_ARRAY_BUFFER );
		glEnableVertexAttribArray( 0 ); // Enable the vertex attribute

		glVertexAttribPointer( ( GLint ) 0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET( 0 ) );

		// Specify the target buffer:
		glBindBufferBase( GL_TRANSFORM_FEEDBACK_BUFFER, 0,
				m_positionsVBO2.getBufferName() );


		m_velocitiesVBO.bindBuffer( GL_ARRAY_BUFFER );
		glEnableVertexAttribArray( 1 ); // Enable the vertex attribute

		glVertexAttribPointer( ( GLint ) 1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET( 0 ) );

		// Specify the target buffer:
		glBindBufferBase( GL_TRANSFORM_FEEDBACK_BUFFER, 1,
				m_velocitiesVBO2.getBufferName() );


		m_livesVBO.bindBuffer( GL_ARRAY_BUFFER );
		glEnableVertexAttribArray( 2 ); // Enable the vertex attribute

		glVertexAttribPointer( ( GLint ) 2, 1, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET( 0 ) );

		// Specify the target buffer:
		glBindBufferBase( GL_TRANSFORM_FEEDBACK_BUFFER, 2,
				m_livesVBO2.getBufferName() );

		// Perform GPU advection
		glBeginTransformFeedback( GL_POINTS );
			glDrawArrays( GL_POINTS, 0, m_nbParticles );
		glEndTransformFeedback();

		// Swap the A and B buffers for ping-ponging, then turn the rasterizer back on:
		m_positionsVBO.swap( m_positionsVBO2 );
		m_velocitiesVBO.swap( m_velocitiesVBO2 );
		m_livesVBO.swap( m_livesVBO2 );
	glDisable( GL_RASTERIZER_DISCARD );

	//--------------------------------------------------------------------------
	// Performs rendering
	//--------------------------------------------------------------------------
	m_renderProgram->useProgram();
	float modelviewMatrix[ 16 ];
	float projectionMatrix[ 16 ];
	//float pointSize;

	// Get the current matrices from OpenGL
	glGetFloatv( GL_MODELVIEW_MATRIX, modelviewMatrix );
	glGetFloatv( GL_PROJECTION_MATRIX, projectionMatrix );
	//glGetFloatv( GL_POINT_SIZE, &pointSize );

	// Send the modelview and projection matrices to the shaders
	m_renderProgram->sendUniform( "modelview_matrix", modelviewMatrix );
	m_renderProgram->sendUniform( "projection_matrix", projectionMatrix );
	//m_renderProgram->sendUniform( "point_size", pointSize );

	string textureName;
	int i = 0;
	for ( list< glTexture2D* >::iterator it = m_textures.begin();
					it != m_textures.end(); ++it )
	{
		glActiveTexture( GL_TEXTURE0 + i );

		(*it)->bindTexture();

		textureName = "texture";
		textureName += i + '0';

		m_renderProgram->sendUniform( textureName.c_str(), i );
		++i;
	}

	m_positionsVBO.bindBuffer( GL_ARRAY_BUFFER );
	glVertexAttribPointer( ( GLint ) 0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET( 0 ) );

	m_velocitiesVBO.bindBuffer( GL_ARRAY_BUFFER );
	glVertexAttribPointer( ( GLint ) 1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET( 0 ) );

	m_livesVBO.bindBuffer( GL_ARRAY_BUFFER );
	glVertexAttribPointer( ( GLint ) 2, 1, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET( 0 ) );

	glDrawArrays( GL_POINTS, 0, m_nbParticles );

	glDisableVertexAttribArray( 0 ); // Enable the vertex attribute
	glDisableVertexAttribArray( 1 ); // Enable the vertex attribute
	glDisableVertexAttribArray( 2 ); // Enable the vertex attribute
}

//------------------------------------------------------------------------------
void glParticleEmitter::addTexture( const char* _texturePath )
{
	glTexture2D* newTexture = new glTexture2D( _texturePath );
	m_textures.push_back( newTexture );
}
